package arkham.data;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.List;

import arkham.util.AHLogger;
import arkham.util.ImageUtils;
import arkham.util.XmlTools;

/*
 * To compile the jaxb classes, run the following in the Arkham folder
 * xjc xml/invcard.xsd -d src -p arkham.jaxb.invcard
 */

/**
 * This class contains all the global values as well as enums for all
 * card types and effects in the game.
 *
 * @author ctheng
 */
public class Globals {

	public final static int SPECIAL_MODIFIER = 999;
	public final static int GATE_SEALED = 14;
	public final static int MONSTER_SURGE = 15;
	public final static int GATE_OPENED = 16;
	public final static int DOES_NOT_MOVE = 0;
	public final static int MOVES_ON_WHITE = 1;
	public final static int MOVES_ON_BLACK = 2;
	public final static int NONE = 0;
	public final static int RESISTANCE = 1;
	public final static int IMMUNITY = 2;

	public static enum GamePhase
	{
		PRE_UPKEEP (0, "Pre-Upkeep"),
		UPKEEP (1, "Upkeep"),
		UPKEEP_EFFECT (2, "Upkeep Effect"),
		MOVEMENT (3, "Movement"),
		ARKHAM_ENCOUNTER (4, "Arkham Encounter"),
		OTHER_WORLD_ENCOUNTER (5, "Other World Encounter"),
		MYTHOS_GATE (6, "Mythos Gate"),
		MYTHOS_CLUE (7, "Mythos Clue"),
		MYTHOS_MOVEMENT (8, "Mythos Movement"),
		MYTHOS_SPECIAL (9, "Mythos Special Movement"),
		MYTHOS_EFFECT (10, "Mythos Effect");

		private int index;
		private String description;

		GamePhase (int index, String desc)
		{
			this.index = index;
			this.description = desc;
		}

		public int index()
		{
			return index;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Checks if a string corresponds to a valid game phase
		 * @param s the string to check
		 * @return true if it corresponds to a game phase, false otherwise
		 */
		public static boolean isValidGamePhase(String s)
		{
			for (GamePhase phase : GamePhase.values())
			{
				if (phase.toString().equalsIgnoreCase(s) ||
						phase.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Gets the game phase by index
		 * @param index the index
		 * @return the game phase corresponding to that index
		 */
		public static GamePhase getGamePhase(int index)
		{
			if (index < 0 || index >= GamePhase.values().length) return null;
			for (GamePhase phase : GamePhase.values())
			{
				if (phase.index() == index)
				{
					return phase;
				}
			}
			return null;
		}

		/**
		 * Gets the game phase by name
		 * @param name the name
		 * @return the game phase with that name or null if not found
		 */
		public static GamePhase getGamePhase(String name)
		{
			for (GamePhase phase : GamePhase.values())
			{
				if (phase.toString().equalsIgnoreCase(name) ||
						phase.name().equalsIgnoreCase(name))
				{
					return phase;
				}
			}
			return null;
		}

	}

	/**
	 * A game setup state is an internal enum used by the client and server
	 * to keep track of the current phase of the game
	 * @author ctheng
	 *
	 */
	public static enum GameSetupState
	{
		PRE_SETUP (0, "Pre-Setup"),
		SELECT_INVESTIGATORS (1, "Select Investigators"),
		SELECT_ANCIENT_ONE (2, "Select Ancient One"),
		RECEIVE_STARTING_ITEMS (3, "Receive Starting Items"),
		FIRST_MYTHOS (4, "First Mythos");

		private int index;
		private String description;

		GameSetupState (int index, String desc)
		{
			this.index = index;
			this.description = desc;
		}

		public int index()
		{
			return index;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Checks if a string corresponds to a valid game setup state
		 * @param s the string to check
		 * @return true if it corresponds to a game setup state, false otherwise
		 */
		public static boolean isValidSetupState(String s)
		{
			for (GameSetupState state : GameSetupState.values())
			{
				if (state.toString().equalsIgnoreCase(s) ||
						state.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Gets the game setup state by index
		 * @param index the index
		 * @return the game setup state corresponding to that index
		 */
		public static GameSetupState getSetupState(int index)
		{
			if (index < 0 || index > GameSetupState.values().length) return null;
			for (GameSetupState state : GameSetupState.values())
			{
				if (state.index() == index)
				{
					return state;
				}
			}
			return null;
		}

		/**
		 * Gets the game setup state by name
		 * @param name the name
		 * @return the game setup state with that name or null if not found
		 */
		public static GameSetupState getSetupState(String name)
		{
			for (GameSetupState state : GameSetupState.values())
			{
				if (state.toString().equalsIgnoreCase(name) ||
						state.name().equalsIgnoreCase(name))
				{
					return state;
				}
			}
			return null;
		}

	}

	/**
	 * This enum describes the 6 skill values an investigator has
	 * @author ctheng
	 *
	 */
	public static enum InvestigatorSkill
	{
		SPEED (0, "Speed"),
		SNEAK (1, "Sneak"),
		FIGHT (2, "Fight"),
		WILL  (3, "Will"),
		LORE  (4, "Lore"),
		LUCK  (5, "Luck");

		private int index;
		private String description;

		InvestigatorSkill(int index, String desc)
		{
			this.index = index;
			description = desc;
		}

		public int index()
		{
			return index;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Checks if a string corresponds to a valid skill
		 * @param s the string to check
		 * @return true if it corresponds to a skill, false otherwise
		 */
		public static boolean isValidSkillType(String s)
		{
			for (InvestigatorSkill skill : InvestigatorSkill.values())
			{
				if (skill.toString().equalsIgnoreCase(s) ||
						skill.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Gets the name of the skill given the index
		 * @param index the index of the skill from 0-5
		 * @return the name of the skill, or empty string on a failure
		 */
		public static String getSkillName(int index)
		{
			if (index < 0 || index > 5) return "";
			for (InvestigatorSkill skill : InvestigatorSkill.values())
			{
				if (skill.index() == index)
				{
					return skill.toString();
				}
			}
			return "";
		}

		/**
		 * Gets the investigator skill by name
		 * @param name the name
		 * @return the skill with that name or null if not found
		 */
		public static InvestigatorSkill getInvestigatorSkill(String name)
		{
			for (InvestigatorSkill skill : InvestigatorSkill.values())
			{
				if (skill.toString().equalsIgnoreCase(name) || skill.name().equalsIgnoreCase(name))
				{
					return skill;
				}
			}
			return null;
		}

		/**
		 * Checks if this is a secondary skill
		 * @param skill the skill to check
		 * @return true if this is a secondary skill
		 */
		public static boolean isSecondarySkill(String skill)
		{
			return skill.equals("Combat") || skill.equals("Horror") || skill.equals("Spell")
				|| skill.equals("Evade");
		}

		/**
		 * Gets the primary skill associated with the secondary skill
		 * @param skill
		 * @return
		 */
		public static InvestigatorSkill getPrimarySkill(String skill)
		{
			if (skill.equals("Combat"))
			{
				return InvestigatorSkill.FIGHT;
			}
			else if (skill.equals("Horror"))
			{
				return InvestigatorSkill.WILL;
			}
			else if (skill.equals("Spell"))
			{
				return InvestigatorSkill.LORE;
			}
			else if (skill.equals("Evade"))
			{
				return InvestigatorSkill.SNEAK;
			}
			else return getInvestigatorSkill(skill);
		}
	}

	/**
	 * This enum describes the conditions on which a monster's power will activate
	 * @author ctheng
	 */
	public static enum MonsterPowerActivation
	{
		FAIL_HORROR ("Fail Horror Check"),
		PASS_HORROR ("Pass Horror Check"),
		FAIL_COMBAT ("Fail Combat Check"),
		PASS_COMBAT ("Pass Combat Check"),
		FAIL_EVADE ("Fail Evade Check"),
		PASS_EVADE ("Pass Evade Check"),
		BEFORE_HORROR ("Before Horror Check"),
		BEFORE_COMBAT ("Before Combat Check"),
		UNCONSCIOUS ("When Knocked Unconscious"),
		INSANE ("When Driven Insane");

		private String description;
		MonsterPowerActivation (String desc)
		{
			description = desc;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Checks if a string corresponds to a valid activation condition
		 * @param s the string to check
		 * @return true if it corresponds to a activation condition, false
		 * otherwise
		 */
		public static boolean isValidActivation(String s)
		{
			for (MonsterPowerActivation activ : MonsterPowerActivation.values())
			{
				if (activ.toString().equalsIgnoreCase(s) ||
						activ.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into an activation condition
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static MonsterPowerActivation getActivationCondition(String s)
		{
			for (MonsterPowerActivation activ : MonsterPowerActivation.values())
			{
				if (activ.toString().equalsIgnoreCase(s) ||
						activ.name().equalsIgnoreCase(s))
				{
					return activ;
				}
			}
			return null;
		}

	}

	/**
	 * This enum represents the kinds of mythos cards
	 * @author ctheng
	 */
	public static enum MythosType
	{
		HEADLINE ("Headline"),
		ENV_URBAN ("Environment (Urban)"),
		ENV_MYSTIC ("Environment (Mystic)"),
		ENV_WEATHER ("Environment (Weather)"),
		RUMOR ("Rumor");

		private String description;
		MythosType (String desc)
		{
			description = desc;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Helper method that checks if this type is an environment
		 * @return true if this mythos type is an environment
		 */
		public boolean isEnvironment()
		{
			return this.equals(MythosType.ENV_MYSTIC) || this.equals(MythosType.ENV_WEATHER)
				|| this.equals(MythosType.ENV_URBAN);
		}

		/**
		 * Checks if a string corresponds to a valid mythos card type
		 * @param s the string to check
		 * @return true if it corresponds to a mythos type, false
		 * otherwise
		 */
		public static boolean isValidMythosType(String s)
		{
			for (MythosType mtype : MythosType.values())
			{
				if (mtype.toString().equalsIgnoreCase(s) ||
						mtype.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into a mythos card type
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static MythosType getMythosType(String s)
		{
			for (MythosType mtype : MythosType.values())
			{
				if (mtype.toString().equalsIgnoreCase(s) ||
						mtype.name().equalsIgnoreCase(s))
				{
					return mtype;
				}
			}
			return null;
		}
	}

	/**
	 * This enum represents the kind of possible monster movements on a mythos
	 * card.
	 * @author ctheng
	 */
	public static enum MythosMovement
	{
		NONE ("No monsters move", 0, 0),
		SQUARE_DIAMOND_WHITE ("Square Diamond/Circle", 0x108, 0x4),
		CIRCLE_WHITE ("Circle/Square Diamond", 0x4, 0x108),
		MOON_WHITE ("Moon/Cross", 0x1, 0x2),
		CROSS_WHITE ("Cross/Moon", 0x2, 0x1),
		HEXAGON_WHITE ("Hexagon/Slash Triangle Star", 0x20, 0xD0),
		SLASH_TRIANGLE_STAR_WHITE("Slash Triangle Star/Hexagon", 0xD0, 0x20),
		ALL_5_WHITE("Circle Cross Slash Triangle Star/Moon Diamond Hexagon Square", 0xD6, 0x129),
		ALL_4_WHITE("Moon Diamond Hexagon Square/Circle Cross Slash Triangle Star", 0x129, 0xD6);

		private String description;
		private int whiteMove, blackMove;

		MythosMovement (String desc, int white, int black)
		{
			description = desc;
			whiteMove = white;
			blackMove = black;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Checks if a string corresponds to a valid mythos movement
		 * @param s the string to check
		 * @return true if it corresponds to a mythos type, false
		 * otherwise
		 */
		public static boolean isValidMythosMovement(String s)
		{
			for (MythosMovement mtype : MythosMovement.values())
			{
				if (mtype.toString().equalsIgnoreCase(s) ||
						mtype.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into a mythos movement
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static MythosMovement getMythosMovement(String s)
		{
			for (MythosMovement mtype : MythosMovement.values())
			{
				if (mtype.toString().equalsIgnoreCase(s) ||
						mtype.name().equalsIgnoreCase(s))
				{
					return mtype;
				}
			}
			return null;
		}

		/**
		 * Returns the long description for display on the info pane
		 * @return the long description
		 */
		public String longDescription()
		{
			if (description.contains("/"))
			{
				String[] tokens = description.split("/");
				return "White: " + tokens[0] + "\nBlack: " + tokens[1];
			}
			else
			{
				return description;
			}
		}

		/**
		 * Gets the movement direction of this dimensional symbol for this
		 * movement type
		 * @param ds the dimensional symbol
		 * @return either MOVES_ON_WHITE, MOVES_ON_BLACK, or DOES_NOT_MOVE
		 */
		public int getMovement(DimensionalSymbol ds)
		{
			if ((whiteMove & (1 << ds.ordinal())) != 0)
			{
				return MOVES_ON_WHITE;
			}
			else if ((blackMove & (1 << ds.ordinal())) != 0)
			{
				return MOVES_ON_BLACK;
			}
			else
			{
				return DOES_NOT_MOVE;
			}
		}

		/**
		 * Gets the movement direction of this monster for this
		 * movement type
		 * @param mon the monster
		 * @return either MOVES_ON_WHITE, MOVES_ON_BLACK, or DOES_NOT_MOVE
		 */
		public int getMovement(Monster mon)
		{
			return getMovement(mon.getSymbol());
		}
	}

	public static enum MoveState
	{
	    /** This is the normal state of movement in Arkham */
	    ARKHAM,
	    /** In this state, the investigator has multiple gates to choose
	     * from to return and has to choose one.
	     */
	    CHOOSE_RETURN_GATE,
	    /** In this state, the investigator will be prompted to
	     * decide if he wishes to encounter the monsters (if any) on
	     * the gate
	     */
	    JUST_RETURNED,
	    /** In this state, the investigator has to deal with monsters in the space
	     * and select which to deal with.
	     */
	    COMBAT,
	    /** Investigators who are trading enter this state */
	    TRADE,
	    /** Game is in this state when the trade recipient is accepting the trade */
	    ACCEPTING_TRADE,
	    /** Investigators not undergoing movement will be in this state */
	    NONE;

	}

	public static enum OtherWorldColorSet
	{
		RED_YELLOW ("Red/Yellow", 3), // r'lyeh
		BLUE_YELLOW ("Blue/Yellow", 9), //yuggoth
		GREEN_YELLOW ("Green/Yellow", 5), // city of great race
		BLUE_RED ("Blue/Red", 10), // abyss
		BLUE_GREEN ("Blue/Green", 12), // celeano
		GREEN_RED ("Green/Red", 6), // plateau of leng
		ALL ("All", 15);

		private String description;
		private int bits; // blue, green, red, yellow (3210)

		OtherWorldColorSet (String desc, int bits)
		{
			this.description = desc;
			this.bits = bits;
		}

		/**
		 * Checks if this color set has a particular gate color
		 * @param color the gate color to check
		 * @return true if it contains the gate color, false otherwise
		 */
		public boolean hasColor(GateColor color)
		{
			return (bits & color.ordinal()) != 0;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Checks if a string corresponds to a valid set of other world colors
		 * @param s the string to check
		 * @return true if it corresponds to a other world color, false otherwise
		 */
		public static boolean isValidOWColorSet(String s)
		{
			for (OtherWorldColorSet colorSet : OtherWorldColorSet.values())
			{
				if (colorSet.toString().equalsIgnoreCase(s) ||
						colorSet.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into a set of other world colors
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static OtherWorldColorSet getOWColorSet(String s)
		{
			for (OtherWorldColorSet colorset : OtherWorldColorSet.values())
			{
				if (colorset.toString().equalsIgnoreCase(s) ||
						colorset.name().equalsIgnoreCase(s))
				{
					return colorset;
				}
			}
			return null;
		}

	}

	public static enum GateColor
	{
		BLUE ("Blue"),
		GREEN ("Green"),
		RED ("Red"),
		YELLOW ("Yellow");

		private String description;

		GateColor(String desc)
		{
			description = desc;
		}

		@Override
		public String toString()
		{
			return description;
		}

		public Color getBgcolor()
		{
		    return XmlTools.translateGateBgcolor(description);
		}

		/**
		 * Checks if a string corresponds to a valid gate color
		 * @param s the string to check
		 * @return true if it corresponds to a gate color, false otherwise
		 */
		public static boolean isValidGateColor(String s)
		{
			for (GateColor color : GateColor.values())
			{
				if (color.toString().equalsIgnoreCase(s) ||
						color.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into a gate color
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static GateColor getGateColor(String s)
		{
			for (GateColor color : GateColor.values())
			{
				if (color.toString().equalsIgnoreCase(s) ||
						color.name().equalsIgnoreCase(s))
				{
					return color;
				}
			}
			return null;
		}


	}

	/**
	 * This enum represents the kind of possible dimensional symbols in the game.
	 * @author ctheng
	 *
	 */
	public static enum DimensionalSymbol
	{
		MOON ("Moon", "img/icons/ds_moon.png"),
		CROSS ("Cross", "img/icons/ds_cross.png"),
		CIRCLE ("Circle", "img/icons/ds_circle.png"),
		SQUARE ("Square", "img/icons/ds_square.png"),
		TRIANGLE ("Triangle", "img/icons/ds_triangle.png"),
		HEXAGON ("Hexagon", "img/icons/ds_hexagon.png"),
		SLASH ("Slash", "img/icons/ds_slash.png"),
		STAR ("Star", "img/icons/ds_star.png"),
		DIAMOND ("Diamond", "img/icons/ds_diamond.png");

		private String text, imgFilename;
		private BufferedImage img;
		DimensionalSymbol (String symbol, String imgFile)
		{
			text = symbol;
			imgFilename = imgFile;
		}

		@Override
		public String toString()
		{
			return text;
		}

		/**
		 * Gets the image filename
		 * @return the image filename
		 */
		public String getImgFilename()
		{
			return imgFilename;
		}

		/**
		 * Gets the icon corresponding to a dimensional symbol. Will cache the image
		 * for future retrievals.
		 * @return the image
		 */
		public BufferedImage getImage()
		{
			if (img != null)
			{
				return img;
			}
			else
			{
				if (imgFilename != null)
				{
					img = ImageUtils.loadImage(imgFilename);
					img = ImageUtils.resizeImage(img, 12, 12);
					return img;
				}
				else
				{
					return null;
				}
			}
		}

		/**
		 * Checks if the string specifies a existing dimensional symbol
		 * @param s The string to check
		 * @return true if it specifies a dimensional symbol
		 */
		public static boolean isValidSymbol(String s)
		{
			for (DimensionalSymbol ds : DimensionalSymbol.values())
			{
				if (ds.toString().equalsIgnoreCase(s) ||
						ds.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into a dimensional symbol
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static DimensionalSymbol getDimensionalSymbol(String s)
		{
			for (DimensionalSymbol ds : DimensionalSymbol.values())
			{
				if (ds.toString().equalsIgnoreCase(s) ||
						ds.name().equalsIgnoreCase(s))
				{
					return ds;
				}
			}
			return null;
		}
	}

	/**
	 * This enum represents the kind of possible monster movements in the game.
	 * @author ctheng
	 *
	 */
	public static enum MonsterMovement
	{
		NORMAL ("Normal", Color.black),
		FAST ("Fast", Color.red),
		STATIONARY ("Stationary", Color.yellow),
		FLYING ("Flying", new Color(0, 199, 200)),
		STALKER ("Stalker", new Color(150, 81, 142)),
		AQUATIC ("Aquatic", new Color(255, 128, 0)),
		SPECIAL ("Special", new Color(32, 192, 32));

		private String description;
		private Color color;

		MonsterMovement(String desc, Color color)
		{
			this.description = desc;
			this.color = color;
		}

		@Override
		public String toString()
		{
			return description;
		}

		public Color getColor()
		{
			return color;
		}

		/**
		 * Checks if the string specifies a existing monster movement
		 * @param s The string to check
		 * @return true if it specifies a monster movement
		 */
		public static boolean isValidMovement(String s)
		{
			for (MonsterMovement mm : MonsterMovement.values())
			{
				if (mm.toString().equalsIgnoreCase(s) ||
						mm.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Translates a string into a monster movement
		 * @param s The string to translate
		 * @return The translated enum constant, or null if not found
		 */
		public static MonsterMovement getMonsterMovement(String s)
		{
			for (MonsterMovement mm : MonsterMovement.values())
			{
				if (mm.toString().equalsIgnoreCase(s) ||
						mm.name().equalsIgnoreCase(s))
				{
					return mm;
				}
			}
			return null;
		}

	}

	/**
	 * This enum represents the frequency of usage of an effect.
	 * @author ctheng
	 *
	 */
	public static enum EffectUsage
	{
		/** Discard after use */
		DISCARD ("Discard"),
		/** Spell that is discarded after use */
		CAST_AND_DISCARD ("Cast and Discard"),
		/** Exhaust after use */
		EXHAUST ("Exhaust"),
		/** Spell that is exhausted after use */
		CAST_AND_EXHAUST ("Cast and Exhaust"),
		/** Return to box after use */
		RETURN_BOX ("Return to box"),
		/** Limited number of uses */
		LIMITED ("Limited"),
		/** Limited number of uses, can use multiple charges at once */
		LIMITED_MULTI("Multiple"),
		/** Always used */
		ALWAYS  ("Always"),
		/** No cost to use, investigator can choose whether to use */
		CHOICE  ("Choice");

		private String description;
		EffectUsage (String desc)
		{
			this.description = desc;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Assigns a usage type to a mod effect and returns true on a success.
		 * @param mod the mod effect
		 * @param usageType the usage string specified in XML
		 * @return true on a success, false on failure to parse usage string
		 */
		public static boolean assignUsage (ModEffect mod, String usageType)
		{
			for (EffectUsage usage : EffectUsage.values())
			{
				if (usage.toString().equalsIgnoreCase(usageType))
				{
					mod.setUsage(usage);
					return true;
				}
			}
			return false;
		}

		/**
        * Assigns a usage type to a usable effect and returns true on a success.
        * @param ueff the usable effect
        * @param usageType the usage string specified in XML
        * @return true on a success, false on failure to parse usage string
        */
       public static boolean assignUsage (UsableEffect ueff, String usageType)
       {
           for (EffectUsage usage : EffectUsage.values())
           {
               if (usage.toString().equalsIgnoreCase(usageType))
               {
                   ueff.setUsage(usage);
                   return true;
               }
           }
           return false;
       }
	}

	public static enum UsablePhase
	{
	    PRE_UPKEEP ("Pre-upkeep"),
	    UPKEEP ("Upkeep"),
	    MOVEMENT ("Movement"),
	    MOVEMENT_ARKHAM ("Movement in Arkham"),
	    MOVEMENT_OW ("Movement in Other Worlds"),
	    COMBAT ("Combat"),
	    ARKHAM_ENCOUNTER ("Arkham Encounter"),
	    OW_ENCOUNTER ("Other World Encounter"),
	    ANY ("Any");

	    private String description;

	    UsablePhase(String desc)
	    {
	        description = desc;
	    }

	    @Override
	    public String toString()
	    {
	        return description;
	    }

	    /**
         * Translates a string into a usable phase
         * @param s The string to translate
         * @return The corresponding usable phase or null if none match
         */
        public static UsablePhase getPhase(String s)
        {
            for (UsablePhase phase : UsablePhase.values())
            {
                if (phase.toString().equalsIgnoreCase(s) ||
                        phase.name().equalsIgnoreCase(s))
                {
                    return phase;
                }
            }
            return null;
        }

        /**
         * Checks if the string specifies a existing usable phase
         * @param s The attribute to check
         * @return true if it specifies a usable phase
         */
        public static boolean isValidAttribute(String s)
        {
            for (UsablePhase phase : UsablePhase.values())
            {
                if (phase.toString().equalsIgnoreCase(s) ||
                        phase.name().equalsIgnoreCase(s))
                {
                    return true;
                }
            }
            return false;
        }

	}

	/** This enum describes all the attributes that monsters can have */
	public static enum MonsterAttribute
	{
		PHYS_RES ("Physical Resistance"),
		PHYS_IMM ("Physical Immunity"),
		MAG_RES ("Magical Resistance"),
		MAG_IMM ("Magical Immunity"),
		ELUSIVE ("Elusive"),
		AMBUSH ("Ambush"),
		NIGHTMARISH ("Nightmarish 1"),
		NIGHTMARISH2 ("Nightmarish 2"),
		OVERWHELMING ("Overwhelming 1"),
		OVERWHELMING2 ("Overwhelming 2"),
		UNDEAD ("Undead"),
		ENDLESS ("Endless"),
		MASK ("Mask");

		private String desc;

		MonsterAttribute (String text)
		{
			desc = text;
		}

		@Override
		public String toString()
		{
			return desc;
		}

		/**
		 * Checks a list of attributes to see if there are any conflicting attributes.
		 * Logs an error with AHLogger if there are conflicting attributes
		 * @param attributes the list of attributes
		 * @return true if there are conflicting attributes, false otherwise
		 */
		public static boolean anyConflictingAttributes(List<MonsterAttribute> attributes)
		{
			boolean nightmarish = false;
			boolean overwhelming = false;
			boolean physdef = false;
			boolean magdef = false;
			boolean[] found = new boolean[MonsterAttribute.values().length];

			Arrays.fill(found, false);
			for (MonsterAttribute attrib : attributes)
			{
				if (found[attrib.ordinal()])
				{
					AHLogger.error("Conflicting monster attributes: duplicate " + attrib.toString());
					return true;
				}
				found[attrib.ordinal()] = true;
				switch (attrib)
				{
				case NIGHTMARISH:
				case NIGHTMARISH2:
					if (nightmarish)
					{
						AHLogger.error("Conflicting monster attributes: multiple nightmarish definitions");
						return true;
					}
					nightmarish = true;
					break;
				case OVERWHELMING:
				case OVERWHELMING2:
					if (overwhelming)
					{
						AHLogger.error("Conflicting monster attributes: multiple overwhelming defintions");
						return true;
					}
					overwhelming = true;
					break;
				case PHYS_RES:
				case PHYS_IMM:
					if (physdef)
					{
						AHLogger.error("Conflicting monster attributes: multiple physical defense defintions");
						return true;
					}
					physdef = true;
					break;
				case MAG_RES:
				case MAG_IMM:
					if (magdef)
					{
						AHLogger.error("Conflicting monster attributes: multiple magical defense defintions");
						return true;
					}
					magdef = true;
					break;
				} // end switch
			} // end for
			return false;
		}

		/**
		 * Translates a string into a monster attribute
		 * @param s The string to translate
		 * @return The corresponding monster attribute or null if none match
		 */
		public static MonsterAttribute getAttribute(String s)
		{
			for (MonsterAttribute ma : MonsterAttribute.values())
			{
				if (ma.toString().equalsIgnoreCase(s) ||
						ma.name().equalsIgnoreCase(s))
				{
					return ma;
				}
			}
			return null;
		}

		/**
		 * Checks if the string specifies a existing monster attribute
		 * @param s The attribute to check
		 * @return true if it specifies a monster attribute
		 */
		public static boolean isValidAttribute(String s)
		{
			for (MonsterAttribute ma : MonsterAttribute.values())
			{
				if (ma.toString().equalsIgnoreCase(s) || ma.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * This enum describes the list of possible subtypes for a unique item
	 * @author ctheng
	 *
	 */
	public static enum UniqueItemSubtype
	{
		PHYS_WEAPON ("Physical Weapon"),
		MAG_WEAPON ("Magical Weapon"),
		TOME ("Tome"),
		MISSION ("Mission"),
		OTHER ("Other");

		private String description;
		UniqueItemSubtype(String desc)
		{
			description = desc;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Translates a string into an enum constant
		 * @param s The string to translate
		 * @return The enum constant
		 */
		public static UniqueItemSubtype getSubType (String s)
		{
			for (UniqueItemSubtype type : UniqueItemSubtype.values())
			{
				if (type.toString().equalsIgnoreCase(s) || type.name().equalsIgnoreCase(s))
				{
					return type;
				}
			}

			return null;
		}

		public static boolean isValidCardType(String s)
		{
			for (UniqueItemSubtype type : UniqueItemSubtype.values())
			{
				if (type.toString().equalsIgnoreCase(s) || type.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * This enum describes the list of possible subtypes for a common item
	 * @author ctheng
	 *
	 */
	public static enum CommonItemSubtype
	{
	    PHYS_WEAPON ("Physical Weapon"),
        MAG_WEAPON ("Magical Weapon"),
		TOME ("Tome"),
		TASK ("Task"),
		OTHER ("Other");

		private String description;
		CommonItemSubtype(String desc)
		{
			description = desc;
		}

		@Override
		public String toString()
		{
			return description;
		}

		/**
		 * Translates a string into an enum constant
		 * @param s The string to translate
		 * @return The enum constant
		 */
		public static CommonItemSubtype getSubType (String s)
		{
			for (CommonItemSubtype type : CommonItemSubtype.values())
			{
				if (type.toString().equalsIgnoreCase(s) || type.name().equalsIgnoreCase(s))
				{
					return type;
				}
			}

			return null;
		}

		public static boolean isValidCardType(String s)
		{
			for (CommonItemSubtype type : CommonItemSubtype.values())
			{
				if (type.toString().equalsIgnoreCase(s) || type.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * Describes the list of investigator card types.
	 * @author ctheng
	 *
	 */
	public static enum InvestigatorCardType
	{
		COMMON ("Common"),
		UNIQUE ("Unique"),
		SPELL  ("Spell"),
		SKILL  ("Skill"),
		ALLY   ("Ally");

		private String typeString;

		private InvestigatorCardType (String type)
		{
			typeString = type;
		}

		public boolean isType(String type)
		{
			return type.equals(typeString);
		}

		@Override
		public String toString()
		{
			return typeString;
		}

		/**
		 * Translates a string into an enum constant
		 * @param s The string to translate
		 * @return The enum constant
		 */
		public static InvestigatorCardType getCardType (String s)
		{
			for (InvestigatorCardType type : InvestigatorCardType.values())
			{
				if (type.toString().equalsIgnoreCase(s) || type.name().equalsIgnoreCase(s))
				{
					return type;
				}
			}

			return null;
		}

		public static boolean isValidCardType (String s)
		{
			for (InvestigatorCardType type : InvestigatorCardType.values())
			{
				if (type.toString().equalsIgnoreCase(s) || type.name().equalsIgnoreCase(s))
				{
					return true;
				}
			}

			return false;
		}
	}

	/* ------------------- String Validation Section ---------------- */

	/**
	 * Validates that the XML 'type' attribute conforms to one of the allowed
	 * types for that particular kind of object
	 * @param objectType The type of object, eg. Environment, Encounter
	 * @param toVerify The string to verify
	 * @return true if the string provided is valid for that object, false
	 * otherwise
	 */
	public static boolean validateType(String objectType, String toVerify)
	{
		//TODO: To implement

		if (objectType.equals("Environment"))
		{
			return true;
		}
		else if (!objectType.equals(""))
		{
			// temporary catchall statement
			return true;
		}
		else
		{
			AHLogger.error("Global.TypeValidator", "Invalid object type: " +
					objectType);
			return false;
		}

	}

}
